抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

React Router

路由原理

前端路由的本质是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新页面,核心是改变视图的同时不会向后端发出请求。目前前端使用的路由就只有两种实现方式:
Hash 模式和 History 模式

Hash 模式

Vue-router 默认是 hash 模式

  • www.test.com/#/ 就是 Hash URL(有 /#/ 就是),当 # 后面的哈希值发生变化时
  • hash 的修改不会导致浏览器刷新,因为 window.location 处理哈希的改变时不会重新渲染页面,而是当作新页面加到历史记录中
  • 所以我们可以通过 hashchange 事件来监听到 URL 的变化,写一些逻辑进行组件替换实现更新页面的效果;同时浏览器监听到 hash 变化,会把更新历史记录,并且按后退键能回到上个位置
  • *无论哈希值如何变化,服务端接收到的 URL 请求永远是 www.test.com
1
2
3
window.addEventListener('hashchange', () => {
// ... 具体逻辑
})

Hash 模式优点:

  • 简单,兼容性也更好(ie8)
  • 不需要服务器端进行任何设置和开发
  • 除了资源加载和ajax请求以外,不会发起其他请求

缺点:

  • 不太美观
  • 对于部分需要重定向的操作,后端无法获取hash部分内容,导致后台无法取得url中的数据,典型的例子就是微信公众号的oauth验证
  • 服务器端无法准确跟踪前端路由信息
  • 对于需要锚点功能的需求会与目前路由机制冲突

History 模式

History 模式是 HTML5 新推出的功能,主要使用 history.pushState 和 history.replaceState 改变 URL。
通过 History 模式改变 URL 同样不会引起页面的刷新,只会更新浏览器的历史记录

1
2
3
4
// 新增历史记录
history.pushState(stateObject, title, URL) // 前两个参数可以写成null,url必须是同源的
// 替换当前历史记录
history.replaceState(stateObject, title, URL)

当用户做出浏览器动作时,比如点击后退按钮时会触发 popState 事件

1
2
3
4
window.addEventListener('popstate', e => {
// e.state 就是 pushState(stateObject) 中的 stateObject
console.log(e.state)
})

两种模式对比

history 的优点:

  • pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;
  • pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
  • pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
  • pushState() 可额外设置 title 属性供后续使用

history 的缺点:一是兼容性,二是需要后端的支持,当真正想输入 url 发起 http 请求的时候,eg. 用户手动输入 URL 后回车

  • hash 模式下,仅 # 之前的内容会被包含在请求中,所以对后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误
  • history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.abc.com/book/id。如果后端缺少对 /book/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”

功能

感觉 React 的路由比 Vue 的路由更复杂些
exect 的区别
<Route>,<Router>,<Route>, https://juejin.im/post/5d53e885f265da03bc1270ee 。应该确实是组件被包裹和 withRouter 是等价的
props.history.push()

Redux

工作流程

  • Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个 Store。
  • State:Store 对象包含所有数据,如果想得到某个时点的数据,就要对 Store 生成快照,这种时点的数据集合,就叫做 State。
  • Action:State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
  • Action Creator:View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦,所以我们定义一个函数来生成 Action,这个函数就叫 Action Creator。
  • Reducer:Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
  • dispatch:是 View 发出 Action 的唯一方法。

然后我们过下整个工作流程:

  • 首先,用户(通过 View)发出 Action,发出方式就用到了 dispatch 方法。
  • 然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer 会返回新的 State
  • State 一旦有变化,Store 就会调用监听函数,来更新 View。

到这儿为止,一次用户交互流程结束。可以看到,在整个流程中数据都是单向流动的,这种方式保证了流程的清晰。

react-redux 工作流程

  • Provider: Provider 的作用是从最外部封装了整个应用,并向 connect 模块传递 store
  • connect: 负责连接 React 和 Redux
    • 获取 state: connect 通过 context 获取 Provider 中的 store,通过 store.getState()获取整个 store tree 上所有 state
    • 包装原组件: 将 state 和 action 通过 props 的方式传入到原组件内部 wrapWithConnect 返回一个 ReactComponent 对象 Connect,Connect 重新 render 外部传入的原组件 WrappedComponent,并把 connect 中传入的 mapStateToProps, mapDispatchToProps 与组件上原有的 props 合并后,通过属性的方式传给 WrappedComponent
    • 监听 store tree 变化: connect 缓存了 store tree 中 state 的状态,通过当前 state 状态和变更前 state 状态进行比较,从而确定是否调用 this.setState()方法触发 Connect 及其子组件的重新渲染

Redux VS Mobx

两者对比:

  • redux 将数据保存在单一的 store 中,mobx 将数据保存在分散的多个 store 中
  • redux 使用 plain object 保存数据,需要手动处理变化后的操作;mobx 适用 observable 保存数据,数据变化后自动处理响应的操作
  • redux 使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx 中的状态是可变的,可以直接对其进行修改
  • mobx 相对来说比较简单,在其中有很多的抽象,mobx 更多的使用面向对象的编程思维;redux 会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
  • mobx 中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而 redux 提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易

场景辨析:

  • mobx 更适合数据不复杂的应用: mobx 难以调试,很多状态无法回溯,面对复杂度高的应用时,往往力不从心.mobx 适合短平快的项目: mobx 上手简单,样板代码少,可以很大程度上提高开发效率.
  • redux 适合有回溯需求的应用: 比如一个画板应用、一个表格应用,很多时候需要撤销、重做等操作,由于 redux 不可变的特性,天然支持这些操作.

参考资料
高频 React 面试题及详解

评论